﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NamedBinaryTag
{
	/// <summary>
	/// Represents a <see cref="System.IO.Stream"/> with a seekable header part.
	/// </summary>
	/// <remarks>
	/// Header part is 1 ko. While the reading process keeps being in this portion, the stream is garanteed to be seekable.
	/// </remarks>
	internal class HeaderedStream : System.IO.Stream
	{
		byte[] headerBuffer;
		long position;
		bool headerCompleted;

		internal System.IO.Stream BaseStream { get; private set; }

		internal HeaderedStream(System.IO.Stream baseStream)
		{
			BaseStream = baseStream;
			headerBuffer = null;
			position = 0;
			headerCompleted = false;
		}

		public override bool CanRead
		{
			get { return BaseStream.CanRead; }
		}

		public override bool CanSeek
		{
			get { return headerCompleted ? true : BaseStream.CanSeek; }
		}

		public override bool CanWrite
		{
			get { return false; }
		}

		public override void Flush()
		{
			BaseStream.Flush();
		}

		public override long Length
		{
			get { return BaseStream.Length; }
		}

		public override long Position
		{
			get
			{
				if (!headerCompleted)
					return position;
				else
					return BaseStream.Position;
			}
			set
			{
				if ((!headerCompleted) && (value < headerBuffer.Length))
					position = value;
				else
				{
					headerCompleted = true;
					BaseStream.Position = value;
				}
			}
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			if (headerBuffer == null)
			{
				byte[] readBuffer = new byte[1024];
				int read = BaseStream.Read(readBuffer, 0, readBuffer.Length);
				headerBuffer = new byte[read];
				Array.Copy(readBuffer, headerBuffer, read);
			}
			if (!headerCompleted)
			{
				if (position + count > headerBuffer.Length)
				{
					count = headerBuffer.Length - (int)position;
				}
				Array.Copy(headerBuffer, (int)position, buffer, offset, count);
				position += count;
				if (position == headerBuffer.Length)
					headerCompleted = true;
				return count;
			}
			else
			{
				return BaseStream.Read(buffer, offset, count);
			}
		}

		public override long Seek(long offset, System.IO.SeekOrigin origin)
		{
			if (!headerCompleted)
			{
				switch (origin)
				{
					case System.IO.SeekOrigin.Begin:
						if (offset < headerBuffer.Length)
						{
							position = offset;
							return position;
						}
						break;
					case System.IO.SeekOrigin.Current:
						if (offset + position < headerBuffer.Length)
						{
							position = Math.Max(0, position + offset);
							return position;
						}
						else
						{
							offset -= (headerBuffer.Length - position);
						}
						break;
				}
			}
			return (position = BaseStream.Seek(offset, origin));
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException();
		}
	}
}
